package com.nineclock.attendance.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.config.PunchUtils;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.threadlocal.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.system.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.OrganizationFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 * @Version 1.0
 */
@Service
@Slf4j
public class AttendPunchServiceImpl implements AttendPunchService {

    @Autowired
    AttendPunchMapper attendPunchMapper;

    @Autowired
    AttendGroupService attendGroupService;

    @Autowired
    OrganizationFeign organizationFeign;

    @Override
    public void addPunch(AttendPunchDTO attendPunchDTO) {
        if(attendPunchDTO == null){
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }

        //获取考勤组对象
        AttendGroupDTO attendGroupDTO = attendGroupService.getAttendGroupByUserId();

        //把dto转换为pojo
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);

        //设置基础信息
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setLat(new BigDecimal(attendPunchDTO.getLat()));
        attendPunch.setLng(new BigDecimal(attendPunchDTO.getLng()));
        attendPunch.setPunchTime(new Date());
        attendPunch.setPunchDateStr(DateUtil.format(new Date(), "yyyy-MM-dd")); //打卡时间 日期（年月日）
        attendPunch.setCreateTime(new Date());
        //上、下午打卡
        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(new Date()) + 1);

        //是否工作日打卡
        boolean validateWorkDay = PunchUtils.validateWorkDay(attendGroupDTO);
        if(!validateWorkDay){
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }


        //是否在有效范围打卡
        boolean flag = PunchUtils.validatePunchArea(attendPunchDTO, attendGroupDTO);
        if(! flag){
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        attendPunch.setAreaValid(flag);

        //是否有效打卡
        boolean effectiveValid = this.validatePunch(attendPunch);
//        attendPunch.setEffectiveValid(effectiveValid);
        if(effectiveValid){
            //设置打卡类型
            PunchUtils.setPunchType(attendPunch, attendGroupDTO);
        }

        attendPunchMapper.insert(attendPunch);
    }

    @Override
    public AttendPunchUserWholeDayDTO queryPunchRecord() {
        AttendPunchUserWholeDayDTO attendPunchUserWholeDayDTO = new AttendPunchUserWholeDayDTO();

        //获取当前登录的员工的 上午 有效打卡记录
        LambdaQueryWrapper<AttendPunch> wrapper1 = new LambdaQueryWrapper<>();
        wrapper1.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        wrapper1.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        wrapper1.eq(AttendPunch::getEffectiveValid, true);
        wrapper1.eq(AttendPunch::getPunchOnOffWork, 1);
        wrapper1.eq(AttendPunch::getPunchDateStr, DateUtil.format(new Date(), "yyyy-MM-dd"));

        AttendPunch attendPunch1 = attendPunchMapper.selectOne(wrapper1);
        if(attendPunch1 != null){
            attendPunchUserWholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(attendPunch1, AttendPunchDTO.class));
        }

        //获取当前登录的员工的 下午 有效打卡记录
        LambdaQueryWrapper<AttendPunch> wrapper2 = new LambdaQueryWrapper<>();
        wrapper2.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        wrapper2.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        wrapper2.eq(AttendPunch::getEffectiveValid, true);
        wrapper2.eq(AttendPunch::getPunchOnOffWork, 2);
        wrapper2.eq(AttendPunch::getPunchDateStr, DateUtil.format(new Date(), "yyyy-MM-dd"));

        AttendPunch attendPunch2 = attendPunchMapper.selectOne(wrapper2);
        if(attendPunch2 != null){
            attendPunchUserWholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(attendPunch2, AttendPunchDTO.class));
        }

        return attendPunchUserWholeDayDTO;
    }

    /**
     * 查询指定时间段内的该企业内所有的员工的考勤情况
     * 1. 获取指定时间段每一天的时间列表
     * 2. 获取该企业内的所有的员工
     * 3. 循环查询每位员工的 考勤情况， 封装数据
     *
     *
     * @param startTime
     * @param endTime
     * @return
     */
    @Override
    public List<AttendPunchDTO> queryMembers(String startTime, String endTime) {
        //获取指定时间段每一天的时间列表
        List<Date> dateList = DateTimeUtil.getDatesBetweenTwoDate(startTime, endTime, "yyyy-MM-dd");
        //把日期列表转换为 字符串日期列表
        List<String> dateStrList = dateList.stream().map(date -> {
            return DateTimeUtil.dateToStr(date, "yyyy-MM-dd");
        }).collect(Collectors.toList());

        //获取该企业内的所有的员工
        List<SysCompanyUserDTO> companyUserList = organizationFeign.queryAllCompanyUser().getData();
        //创建返回值对象
        List<AttendPunchDTO> attendPunchDTOList = new ArrayList<>();

        if(CollectionUtil.isNotEmpty(companyUserList)){
            for (SysCompanyUserDTO companyUserDTO : companyUserList) {
                AttendPunchDTO attendPunchDTO = BeanHelper.copyProperties(companyUserDTO, AttendPunchDTO.class);
                //查询 该员工 指定时间段内的 所有考勤情况
                LambdaQueryWrapper<AttendPunch> wrapper = new LambdaQueryWrapper<>();
                wrapper.eq(AttendPunch::getCompanyId, companyUserDTO.getCompanyId()); //企业id
                wrapper.eq(AttendPunch::getCompanyUserId, companyUserDTO.getId()); //员工id
                //打卡记录在 时间段内
                wrapper.between(AttendPunch::getPunchDateStr, startTime, endTime);
                wrapper.eq(AttendPunch::getEffectiveValid, true); // 是否有效打卡
                // 该员工 指定时间段内的 所有有效打卡记录
                List<AttendPunch> attendPunchList = attendPunchMapper.selectList(wrapper);
                //对该员工 指定时间段内的 所有有效打卡记录 ， 根据日期 分组
                //map<key: 日期 ， value： 该日期对应的（上下）打卡记录>
                Map<String, List<AttendPunch>> map = attendPunchList.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));
                //封装 每一天的考勤情况
                List<AttendsDTO> attendsList = new ArrayList<>();
                for (String dateStr : dateStrList) {
                    AttendsDTO attendsDTO = new AttendsDTO();
                    attendsDTO.setAttendDate(dateStr);
                    //获取 某一天的 打卡记录
                    List<AttendPunch> attendPunches = map.get(dateStr);
                    attendsDTO.setAttendStatus(this.handleAttendStatus(attendPunches));

                    attendsList.add(attendsDTO);
                }

                attendPunchDTO.setAttendsList(attendsList);

                attendPunchDTOList.add(attendPunchDTO);
            }
        }


        return attendPunchDTOList;
    }

    @Override
    public void export(String startTime, String endTime) throws IOException {

        List<AttendPunchDTO> attendPunchDTOList = this.queryMembers(startTime, endTime);

        WriteCellStyle cellStyle = new WriteCellStyle();
        //表头和内容都是水平居中
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short)12);
        cellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(cellStyle,cellStyle);

        //设置文件下载的头信息
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        response.setHeader("Content-disposition", "attachment;filename=Attendance.xlsx");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");

        EasyExcel.write(response.getOutputStream())
                .head(header(attendPunchDTOList))
                .registerWriteHandler(styleStrategy)
                .registerWriteHandler(new MyColumnStyle())
                .sheet(0)
                .doWrite(data(attendPunchDTOList));


    }

    //表头
    private List<List<String>> header(List<AttendPunchDTO> attendPunchDTOList) {
        List<List<String>> head = new ArrayList<>();
        head.add(Arrays.asList("姓名"));
        head.add(Arrays.asList("工号"));
        head.add(Arrays.asList("部门"));
        head.add(Arrays.asList("职位"));

        AttendPunchDTO attendPunchDTO = attendPunchDTOList.get(0);
        List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
        for (AttendsDTO attendsDTO : attendsList) {
            String attendDate = attendsDTO.getAttendDate();
            head.add(Arrays.asList(attendDate));
        }
        return head;
    }
    //列表数据
    private List<List<String>> data(List<AttendPunchDTO> attendPunchDTOList) {
        List<List<String>> data = new ArrayList<>();

        for (AttendPunchDTO attendPunchDTO : attendPunchDTOList) {
            List<String> list =new ArrayList<>();
            list.add(attendPunchDTO.getUserName());
            list.add(attendPunchDTO.getWorkNumber());
            list.add(attendPunchDTO.getDepartmentName());
            list.add(attendPunchDTO.getPost());

            List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
            for (AttendsDTO attendsDTO : attendsList) {
                String attendStatus = attendsDTO.getAttendStatus();
                list.add(attendStatus);
            }

            data.add(list);
        }


        return data;
    }

    private String handleAttendStatus(List<AttendPunch> punches) {
        String amAttendStatus = "缺卡";//上午 - 考勤
        String pmAttendStatus = "缺卡";//下午 - 考勤
        if(CollectionUtil.isNotEmpty(punches)){
            for (AttendPunch punch : punches) {
                if(punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()){
                    amAttendStatus = this.getPushDesc(punch.getMorningPunchType());
                }else if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()){
                    pmAttendStatus = this.getPushDesc(punch.getAfternoonPunchType());
                }
            }
        }
        return amAttendStatus+"/"+pmAttendStatus;
    }


    private String getPushDesc(Integer morningPunchType) {
        String desc = "";
        switch (morningPunchType){
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }

    /**
     * 1. 查询打卡记录，如果没有查询到，无论何时打卡都是有效的
     * 2. 查询到了
     *  如果是上午打卡， 则无效
     *  如果是下午打卡， 以最后一次打卡记录为准, 之前打卡记录无效
     * @param attendPunch
     * @return
     */
    private boolean validatePunch(AttendPunch attendPunch) {
        LambdaQueryWrapper<AttendPunch> punchQw = new LambdaQueryWrapper<>();

        punchQw.eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork());
        punchQw.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        punchQw.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        punchQw.eq(AttendPunch::getPunchDateStr , attendPunch.getPunchDateStr());

        //查询 上/下午 打卡次数
        Integer count = attendPunchMapper.selectCount(punchQw);
        //如果 打卡次数为0 ， 无论何时打卡都是有效打卡
        if(count == 0){
            attendPunch.setEffectiveValid(true);
        }else{
            //如果打卡次数不为0， 且是上午打卡 ， 所以打卡无效
            if(attendPunch.getPunchOnOffWork() == 1){
                attendPunch.setEffectiveValid(false);
            }else{
                //否则 下午打卡，且次数不为0， 本次打卡有效
                attendPunch.setEffectiveValid(true);
                //更新下午其他的打卡为无效打卡
                AttendPunch updateAttendPunch = new AttendPunch();
                updateAttendPunch.setEffectiveValid(false);
                attendPunchMapper.update(updateAttendPunch, punchQw);
            }
        }

        return attendPunch.getEffectiveValid();
    }


    class MyColumnStyle extends AbstractColumnWidthStyleStrategy {
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList,
                                      Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 4500);
        }
    }
}
